繼昨天的 Proxy 之後,要提到這個和常常和 Proxy 一起使用的語法:同樣是 ES6 推出的新關鍵字,Reflect。
Reflect 本身並不是一個建構函式,所以我們沒辦法使用 new 來建立一個新的 Reflect 物件。
它更像是一個靜態方法的集合,要使用有關它的方法時就像使用 Math 函式一樣,直接使用靜態地調用相關功能,而無需 new 來建立。
Reflect 這個關鍵字要解決的問題是:
Proxy 攔截器 1 : 1 對應的操作Object.defineProperty(),Function.apply()
Reflect 上的操作都變成靜態函式的呼叫,如 Object 以前用 get 會像是 obj.property,在 Reflect上變成 Reflect.get(obj, property)。Reflect 上的靜態方法的第一個參數都必須是物件,否則會拋出 TypeError 的例外。(如 Uncaught TypeError: Reflect.get called on non-object)Reflect 上會拋出例外的幾乎只剩下上面所提到的第一個參數傳入不是物件時,其餘本來在物件上相同方法會拋出例外的時候,Reflect 都改為回傳 false。成功的時候依據方法不同,會選擇回傳值或是 true 表示成功,Reflect 提供的靜態方法主要有以下幾種:
這些方法應該看起來很眼熟 -- 因為在昨天的 Proxy 大多應該都看過了,實際上昨天我的 Proxy 提供的攔截器沒有寫全,Proxy 能攔截的和 Reflect 會完全 1 : 1 對應,截至撰文時間點,共有 13 k個方法。
1 : 1 對應的好處是讓使用者能在做完條件判斷後直接無需記憶本來的內部方法(因為 Proxy 攔截的對象多為內部方法,使用方式可能不是直接呼叫名字,如 get),而直接用 Reflect.get 這樣的方法來處理。
抽昨天的例子,調換一部份的內容來展示這種寫法,寫起來會像是這樣:
//經紀人
const agentHandler = {
//攔截對明星資訊的訪問
get(target, property) {
//只有當詢問演唱會時間才會回答
if (property === 'nextConcert') {
return Reflect.get(target, property);//透過 Reflect 來使用
} else {
//其他一概不知道
console.log(`Proxy get the request to : ${property} and block.`);
return undefined;
}
}
};
本來返回演唱會時間的語句可能要寫成 return target.property;,是用 . 去做訪問。
但透過 Reflect 卻可以寫成 return Reflect.get(target, property);,Reflect.get 的方法名稱與攔截器的 get 名稱統一了,意圖更加明確,且無需額外記憶本來的寫法。
關於 Reflect 的各個靜態方法,我這邊就不一一介紹,這種方法詳解請直接參考 MDN 文件。
會依據方法回傳處理上有些不同,但多遵循上面提到的 3.、4. 規則,使用時再確認文件多注意即可。
有些文章會提到,透過組合 Proxy 和 Reflect,JS 得以進行「元程式設計」。
剛好有機會來聊聊這個概念。
「元」這個詞看上去相當難懂,如果單查 wiki 可能會被繞來繞去的名詞搞昏。
meta 一詞語意的語源來自於形上學的外語構詞 meta-physic,用來只對現象本質的探求(From Wiki)。
有個詞叫metadata在程式領域也會看到(如網站標籤,這個詞中文直接翻譯會翻成詮釋資料/後設資料),簡單的說可以解釋為「資料的資料」的概念,對書籍來說,本身書籍已經是內容的載體,而通常書籍我們更關注的是內容。但也有像標題、出版社這些書籍(資料)的資料,這些我們就會稱作元的概念。
對應到程式設計,可以從另一個名詞來了解:「自修改程式設計」。
用上面資料的解釋,套用到程式上面,就是指能操作程式碼的程式碼,聽起來很拗口對吧?
這就是自修改程式設計的概念:在執行期 runtime的時候,透過程式碼去操控程式碼的反應行為。
自修改程式設計的英文是 reflective programming -- 所以也譯作反射式程式設計。
我想這也是為什麼 JS 這個功能的關鍵字會被命名為 Reflect,這個詞在程式設計裡往往都是具備這樣的意思,現在如果你有興趣的話,再去看元程式設計的 Wiki,相信解釋上就不會那麼讓人困惑,他指的就是相近的事。定義上來說,元程式設計的範圍更廣,反射式程式設計是專注於執行期自修改與檢查其中的一種實踐方法。
過去的 eval() 語法也能做到一樣的事(runtime 時透過程式改變程式行為),但近乎是沒有規範的,使用的危險程度上、造成意外的程度上都比 Proxy + Reflect更加嚴重。
在 ES6 推出 Proxy + Reflect 的組合後,Proxy 讓我們有了更好的工具來對內部方法添加合適的實作,包含驗證、預設、觀察等等,Reflect 讓我們能在撰寫 Proxy 相關程式的時候更加直觀,且整合散落各處的物件、函式相關方法為同一規範,讓元程式設計在 JS 中更能夠被好好實作。